package projorientado.image;

public class UnsolvedImageLayerData {
    private float[][] toBeSolved;
    private int toBeSolvedWidth, toBeSolvedHeight;
    private int newWidth, newHeight;
    private float[][] solved;
    private static final float MAXVALUE = 1.0f;
    private static final float MINVALUE = 0.0f;
    public static final int NO_SOLUTION = 0;
    public static final int LINEAR_SOLUTION = 1;
    public static final int QUADRATIC_SOLUTION = 2;

    
    public UnsolvedImageLayerData(float[][] toBeSolved, int width, int height) {
        setToBeSolvedData(toBeSolved);
        solved = null;
        try {
            setWidth(width);
            setHeight(height);
        } catch(Exception e) {
            System.out.println(e);
            toBeSolved = null;
            newHeight = -1;
            newWidth = -1;
        }
    }
    
    public void setToBeSolvedData(float[][] data) {
        this.toBeSolved = data;
        toBeSolvedWidth = toBeSolved.length;
        toBeSolvedHeight = toBeSolved[0].length;
    }
    
    public void setWidth(int width) throws Exception {
        if (this.toBeSolved == null) {
            throw new Exception("Defina a imagem a ser resolvida primeiro");
        }
        if (this.toBeSolved.length > width) {
            throw new Exception("A nova imagem não pode ser menor que a original");
        }
        this.newWidth = width;
    }
    
    public void setHeight(int height) throws Exception {
        if (this.toBeSolved == null) {
            throw new Exception("Defina a imagem a ser resolvida primeiro");
        }
        if (this.toBeSolved[0].length > height) {
            throw new Exception("A nova imagem não pode ser menor que a original");
        }
        this.newHeight = height;
    }
    
    public boolean isSolved() {
        return solved != null;
    }
    
    public  ImageLayerData solve(int solution_type) throws Exception {
        if (toBeSolved == null || newHeight <= 0 || newWidth <= 0) {
            throw new Exception("Parametros não definidos");
        }
        solved = new float[newWidth][newHeight];
        buildData();
        switch (solution_type) {
            case LINEAR_SOLUTION: solveLinear(); break;
            case QUADRATIC_SOLUTION: solveQuadratic(); break;
            default: dontSolve();
        }
        ImageLayerData result = new ImageLayerData(solved);
        return result;
    }
    
    private void dontSolve() {
        for (int x = 0; x < newWidth ; x++) {
            for (int y = 0; y < newHeight ; y++) {
                if (solved[x][y] == -1.0f)
                    solved[x][y] = 0;
            }
        }
    }
    
    private void solveLinear() {
        for (int y = 0; y < newHeight ; y++) {
            while (y < newHeight && solved[0][y] == -1) y++;
            int x0=0, x1;
            float f0, f1;
            while (x0 < newWidth - 2) {
                while (x0 < newWidth - 2 && solved[x0][y] == -1) x0++;
                x1 = x0+1;
                while (x1 < newWidth && solved[x1][y] == -1) x1++;
                if (x1 < newHeight) {
                    f0 = solved[x0][y];
                    f1 = solved[x1][y];
                    horizontalLinearInterpolation(y, x0, f0, x1, f1);
                }
                x0 = x1;
            }
        }
        
        for (int x = 0; x < newWidth ; x++) {
            int y0=0, y1;
            float f0, f1;
            while (y0 < newHeight - 2) {
                while (y0 < newHeight - 2 && solved[x][y0] == -1) y0++;
                y1 = y0+1;
                while (y1 < newHeight && solved[x][y1] == -1) y1++;
                if (y1 < newHeight) {
                    f0 = solved[x][y0];
                    f1 = solved[x][y1];
                    verticalLinearInterpolation(x, y0, f0, y1, f1);
                }
                y0 = y1;
            }
        }
    }
    
    private void solveQuadratic() {
        for (int y = 0; y < newHeight ; y++) {
            while (y < newHeight && solved[0][y] == -1) y++;
            int x0=0, x1, x2;
            float f0, f1, f2;
            x1 = x0+1;
            while (x1 < newWidth && solved[x1][y] == -1) x1++;
            x2 = x1 + 1;
            while (x0 < newWidth - 2) {
                while (x2 < newWidth && solved[x2][y] == -1) x2++;
                if (x2 >= newWidth) {
                    if (x1 < newHeight) {
                    f0 = solved[x0][y];
                    f1 = solved[x1][y];
                    horizontalLinearInterpolation(y, x0, f0, x1, f1);
                    }
                } else {
                    f0 = solved[x0][y];
                    f1 = solved[x1][y];
                    f2 = solved[x2][y];
                    horizontalQuadraticInterpolation(y, x0, f0, x1, f1, x2, f2);
                }
                x0 = x1;
                x1 = x2;
                x2++;
            }
        }
        
        for (int x = 0; x < newWidth ; x++) {
            int y0=0, y1, y2;
            float f0, f1, f2;
            y1 = y0+1;
            while (y1 < newHeight && solved[x][y1] == -1) y1++;
            y2 = y1+1;
            while (y0 < newHeight - 2) {
                while (y2 < newHeight && solved[x][y2] == -1) y2++;
                if (y2 >= newHeight) {
                    if (y1 < newHeight) {
                    f0 = solved[x][y0];
                    f1 = solved[x][y1];
                    verticalLinearInterpolation(x, y0, f0, y1, f1);
                    }
                } else {
                    f0 = solved[x][y0];
                    f1 = solved[x][y1];
                    f2 = solved[x][y2];
                    verticalQuadraticInterpolation(x, y0, f0, y1, f1, y2, f2);
                }
                y0 = y1;
                y1 = y2;
                y2++;
            }
        }
    }
    
    private void setPos(int x, int y, float value) {
        if (value > MAXVALUE) {
            solved[x][y] = MAXVALUE;
        } else if (value < MINVALUE) {
            solved[x][y] = MINVALUE;
        } else {
            solved[x][y] = value;
        }
    }
    
    private void horizontalQuadraticInterpolation(int y, int x0, float f0, int x1Abs, float f1, int x2Abs, float f2) {
        int x1 = x1Abs - x0;
        int x2 = x2Abs - x0;
        float deltaF0 = f1-f2;
        float deltaF1 = f2-f1;
        float k = x2*x2-x2*x1;
        float a = (deltaF0*(1-(x2/x1)+deltaF1))/k;
        float b = (deltaF0/x1)+(deltaF0*(x2-x1)-deltaF1*x1)/k;
        for (int x = x0+1; x < x1Abs; x++) {
            setPos(x,y, a*x*x+b*x+f0);
        }
        //for (int x = x1Abs+1; x < x2Abs; x++) {
        //    setPos(x,y, a*x*x+b*x+f0);
       //}
    }
   
    
    private void verticalQuadraticInterpolation(int x, int y0, float f0, int y1Abs, float f1, int y2Abs, float f2) {
        int y1 = y1Abs - y0;
        int y2 = y2Abs - y0;
        float deltaF0 = f1-f2;
        float deltaF1 = f2-f1;
        float k = y2*y2-y2*y1;
        float a = (deltaF0*(1-(y2/y1)+deltaF1))/k;
        float b = (deltaF0/y1)+(deltaF0*(y2-y1)-deltaF1*y1)/k;
        for (int y = y0+1; y < y1Abs; y++) {
            setPos(x,y, a*y*y+b*y+f0);
        }
        //for (int y = y1Abs+1; y < y2Abs; y++) {
        //    setPos(x,y, a*y*y+b*y+f0);
       //}
    }
    
    private void horizontalLinearInterpolation(int y, int x0, float f0, int x1Abs, float f1) {
        int x1 = x1Abs - x0;
        float a = (f1-f0)/x1;
        for (int x = x0+1; x < x1Abs; x++) {
            setPos(x,y, a*x+f0);
        }
    }
    
    private void verticalLinearInterpolation(int x, int y0, float f0, int y1Abs, float f1) {
        int x1 = y1Abs - y0;
        float a = (f1-f0)/x1;
        for (int y = y0+1; y < y1Abs; y++) {
            setPos(x,y, a*y+f0);
        }
    }
    
    private boolean[] referenceOnX() {
        boolean[] result = new boolean[newWidth];
        int fixedAmount = (newWidth-toBeSolvedWidth)/(toBeSolvedWidth-1);
        int addFactorReference = (newWidth-toBeSolvedWidth)%(toBeSolvedWidth-1);
        result[0] = true;
        int xReferenceIterator = 1;
        for (int k = 1; k < toBeSolvedWidth; k++) {
            int actualAmout = fixedAmount;
            if ( k <= addFactorReference) actualAmout++;
            for (int i = 0; i < actualAmout; i++) {
                result[xReferenceIterator] = false;
                xReferenceIterator++;
            }
            result[xReferenceIterator] = true;
            xReferenceIterator++;
        }
        return result;
    }
    
    private boolean[] referenceOnY() {
        boolean[] result = new boolean[newHeight];
        int fixedAmount = (newHeight-toBeSolvedHeight)/(toBeSolvedHeight-1);
        int addFactorReference = (newHeight-toBeSolvedHeight)%(toBeSolvedHeight-1);
        result[0] = true;
        int yReferenceIterator = 1;
        for (int k = 1; k < toBeSolvedHeight; k++) {
            int actualAmout = fixedAmount;
            if ( k <= addFactorReference) actualAmout++;
            for (int i = 0; i < actualAmout; i++) {
                result[yReferenceIterator] = false;
                yReferenceIterator++;
            }
            result[yReferenceIterator] = true;
            yReferenceIterator++;
        }
        return result;
    }
    
    private void buildData() {
        boolean[] xReference = referenceOnX();
        boolean[] yReference = referenceOnY();
        int xTBS = 0;
        int yTBS = 0;
        for (int x = 0; x < newWidth; x++) {
            for (int y = 0; y < newHeight; y++) {
                if (xReference[x] && yReference[y]) {
                    solved[x][y] = toBeSolved[xTBS][yTBS];
                    yTBS++;
                } else {
                    solved[x][y] = -1;
                }
            }
            if (x+1<newWidth && xReference[x+1]== true) xTBS++;
            yTBS = 0;
        }
    }
}

